/* Copyright 2009 (c) ENSI de Bourges
 * 		88 boulevard Lahitolle, 18020 Bourges Cedex, France
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
/**
 * \file frame-private.c
 * \brief Source code for the LiveSecurity frames' private functions
 * \author Steve Dodier <sidnioulz@gmail.com>
 *
 * This source code file contains private utility functions for LiveSecurity
 * frames.
 */
#include <gtk/gtk.h>
#include <stdlib.h>
#include <unistd.h>

#include "application-data.h"
#include "frame-common.h"
#include "frame-private.h"
#include "frame-signals.h"
#include "frame-utilities.h"
#include "gedit-spinner.h"
#include "machine.h"
#include "time-period.h"



void frame_on_exit (GtkWidget *button, gpointer user_data)
{
	frame_t *f = (frame_t *) user_data;
	ls_instance_t *inst = livesecurity_instance ();

	frame_list_remove (&(inst->frames_list), f);
}



char *frame_get_display_name_only (char *type)
{
	char		*err		= NULL;
	char		*path		= g_strdup_printf ("lib"APP_NAME_LOWER"%s.so", type);
	char		*(*func)()	= NULL;
	void		*handle		= dlopen (path, RTLD_LAZY);
	g_free (path);
	err = dlerror ();
	if (err) {
		g_warning ("frame_get_display_name_only: %s", err);
		return NULL;
	}

	*(void **) (&func) = dlsym (handle, "get_display_name");
	err = dlerror ();
	if (err) {
		g_warning ("frame_get_display_name_only: %s", err);
		dlclose (handle);
		return NULL;
	}

	return func();
}



frame_t *frame_new (char *type)
{
	char		*err	= NULL;
	char		*path	= g_strdup_printf ("lib"APP_NAME_LOWER"%s.so", type);
	frame_t		*frame	= g_malloc (sizeof (frame_t));

	frame->handle = dlopen (path, RTLD_LAZY);
	g_free (path);
	err = dlerror ();
	if (err) {
		g_warning ("frame_new: %s", err);
		g_free (frame);
		return NULL;
	}

	*(void **) (&(frame->build_func_list)) = dlsym (frame->handle, "build_func_list");
	err = dlerror ();
	if (err) {
		g_warning ("frame_new: %s", err);
		g_free (frame);
		dlclose (frame->handle);
		return NULL;
	}

	*(void **) (&(frame->build_time_periods)) = dlsym (frame->handle, "build_time_periods");
	err = dlerror ();
	if (err) {
		g_warning ("frame_new: %s", err);
		g_free (frame);
		dlclose (frame->handle);
		return NULL;
	}
	
	*(void **) (&(frame->is_refreshing_needed)) = dlsym (frame->handle, "is_refreshing_needed");
	err = dlerror ();
	if (err) {
		g_warning ("frame_new: %s", err);
		dlclose (frame->handle);
		g_free (frame);
		return NULL;
	}
	
	*(void **) (&(frame->refresh_content)) = dlsym (frame->handle, "refresh_content");
	err = dlerror ();
	if (err) {
		g_warning ("frame_new: %s", err);
		dlclose (frame->handle);
		g_free (frame);
		return NULL;
	}

	*(void **) (&(frame->init_private)) = dlsym (frame->handle, "init_private");
	err = dlerror ();
	if (err) {
		g_warning ("frame_new: %s", err);
		dlclose (frame->handle);
		g_free (frame);
		return NULL;
	}

	*(void **) (&(frame->free_private)) = dlsym (frame->handle, "free_private");
	err = dlerror ();
	if (err) {
		g_warning ("frame_new: %s", err);
		dlclose (frame->handle);
		g_free (frame);
		return NULL;
	}

	*(void **) (&(frame->display_machine_changed)) = dlsym (frame->handle, "display_machine_changed");
	err = dlerror ();
	if (err) {
		g_warning ("frame_new: %s", err);
		dlclose (frame->handle);
		g_free (frame);
		return NULL;
	}

	*(void **) (&(frame->time_period_changed)) = dlsym (frame->handle, "time_period_changed");
	err = dlerror ();
	if (err) {
		g_warning ("frame_new: %s", err);
		dlclose (frame->handle);
		g_free (frame);
		return NULL;
	}

	*(void **) (&(frame->frame_size_changed)) = dlsym (frame->handle, "frame_size_changed");
	err = dlerror ();
	if (err) {
		g_warning ("frame_new: %s", err);
		dlclose (frame->handle);
		g_free (frame);
		return NULL;
	}

	*(void **) (&(frame->draw_area)) = dlsym (frame->handle, "draw_area");
	err = dlerror ();
	if (err) {
		g_warning ("frame_new: %s", err);
		dlclose (frame->handle);
		g_free (frame);
		return NULL;
	}

	*(void **) (&(frame->get_display_name)) = dlsym (frame->handle, "get_display_name");
	err = dlerror ();
	if (err) {
		g_warning ("frame_new: %s", err);
		dlclose (frame->handle);
		g_free (frame);
		return NULL;
	}

	frame->type = g_strdup (type);
	return frame;
}



void frame_free (frame_t *f)
{
	if (f) {
		f->compute = FALSE;
		g_cond_signal (f->cond);
		g_thread_join (f->computing_thread);
		g_mutex_free (f->mutex);
		g_cond_free (f->cond);
		f->free_private (f);
		if (f->spinner_stop_src)
			g_source_remove (f->spinner_stop_src);
		if (f->spinner_start_src)
			g_source_remove (f->spinner_start_src);
		//FIXME possible memleak with the spinner
		g_object_unref (f->builder);
		g_object_unref (f->signal_box);
		dlclose (f->handle);
		g_free (f->type);
		g_free (f);
	}
}



void frame_free_uninitialised (frame_t *f)
{
	if (f) {
		dlclose (f->handle);
		g_free (f->type);
		g_free (f);
	}
}



double frame_timed_refresh_content (frame_t *f, int *call_result)
{
	GTimer *chrono = g_timer_new ();

	if (call_result != NULL)
		*call_result = f->refresh_content (f);
	else
		f->refresh_content (f);

	g_timer_stop (chrono);

	double result = g_timer_elapsed (chrono, NULL);

	g_timer_destroy (chrono);
	
	return result;
}



void frame_update_size_button (frame_t *f)
{
	GtkWidget 			*button 		= NULL;	
	GtkWidget 			*size_image		= NULL;	

	button = (GtkWidget *) gtk_builder_get_object (f->builder, "size_toggle");

	if (f->size == NORMAL)
		size_image = gtk_image_new_from_stock ("gtk-zoom-in", GTK_ICON_SIZE_BUTTON);
	else if (f->size == LARGE)
		size_image = gtk_image_new_from_stock ("gtk-zoom-out", GTK_ICON_SIZE_BUTTON);
	else // FULL SCREEN
		size_image = gtk_image_new_from_stock ("gtk-leave-fullscreen", GTK_ICON_SIZE_BUTTON);

	gtk_button_set_image (GTK_BUTTON (button), size_image);
}



void frame_machine_changed (GtkComboBox *box, gpointer user_data)
{
	frame_t *f = NULL;
	GtkTreeIter iter;
	GtkTreeModel *model;
	GValue val = {0,};

	f = (frame_t *) user_data;

	if (gtk_combo_box_get_active_iter (box, &iter)) {
		model = gtk_combo_box_get_model (box);
		gtk_tree_model_get_value (model, &iter, 1, &val);
		
		f->display_machine = (machine_t *) g_value_get_pointer (&val);
		g_value_unset (&val);
	}

	f->display_machine_changed (f);
}



void frame_time_period_changed (GtkComboBox *box, gpointer user_data)
{
	frame_t *f = NULL;
	GtkTreeIter iter;
	GtkTreeModel *model;
	GValue value = {0,};

	f = (frame_t *) user_data;

	if (gtk_combo_box_get_active_iter (box, &iter)) {
		model = gtk_combo_box_get_model (box);

		gtk_tree_model_get_value (model, &iter, 1, &value);
		f->display_period.type = g_value_get_int (&value);
		g_value_unset (&value);

		gtk_tree_model_get_value (model, &iter, 2, &value);
		f->display_period.value = g_value_get_long (&value);
		g_value_unset (&value);
	}

	f->time_period_changed (f);
}



gboolean frame_button_event (GtkWidget *area, 
                                GdkEventButton *event,
                                gpointer user_data)
{
	return FALSE;
}



gboolean frame_expose_handler (GtkWidget *area,
								GdkEventExpose *event,
								gpointer user_data)
{
	frame_t *f = NULL;
	cairo_t *cr = NULL;

	f = (frame_t *) user_data;

	cr = gdk_cairo_create (area->window);
	cairo_rectangle (cr, event->area.x, event->area.y, 
					event->area.width, event->area.height);
	cairo_clip (cr);

	f->draw_area (f, cr);

	cairo_destroy (cr);

	printf ("kitteh has been exposed to cheezburgers\n");

	return FALSE;
}



void frame_size_changed_event (GtkWidget *button,
								gpointer user_data)
{
	frame_t *f 	= NULL;
	GtkWidget 	*area 	= NULL;

	f = (frame_t *) user_data;
	
	if (f->size == NORMAL)
		f->size = LARGE;
	else 
		f->size = NORMAL;
	
	frame_update_size_button (f);

	area = (GtkWidget *) gtk_builder_get_object (f->builder, "drawing_area");
	gtk_widget_set_size_request (area, frame_get_width (f),
										frame_get_height (f));
	gtk_widget_queue_resize (frame_get_root_widget (f));

	f->frame_size_changed (f);
}



static gboolean frame_spinner_stop (gpointer data)
{
	frame_t		*f			= (frame_t *) data;
	g_signal_emit_by_name (f->signal_box, "spinner_stop", data);
	return FALSE;
}



static gboolean frame_spinner_start (gpointer data)
{
	frame_t		*f			= (frame_t *) data;
	g_signal_emit_by_name (f->signal_box, "spinner_start", data);
	return FALSE;
}



gpointer frame_computing_thread (gpointer data)
{
	ls_instance_t *inst = livesecurity_instance ();
	frame_t		*f			= (frame_t *) data;
	GTimeVal	timeval;
	gboolean	do_refresh = FALSE;

	while (f->compute) {
		g_mutex_lock (f->mutex);

		g_get_current_time (&timeval);
		if (inst->settings->refresh_freq == REFRESH_FREQ_AUTO) {
			/* When timeout is 0, we decrease it X times by 1, so we can simulate
			 * X passes of an algorithm that will calculate the total waiting time
			 * before a refreshing was needed. Once we did that X times, we can
			 * divide the sum by X to get an average waiting time between each
			 * refresh. Then, we will set timeout to 10 minutes and decrease it
			 * each time by the average time found.
			 */
			if (f->avg_refresh_timeout <= 0) {
				// Initialising the calculation mode
				GTimer *elapsed = g_timer_new ();
				g_timer_start (elapsed);
				if (f->avg_refresh_timeout == 0)
					f->avg_refresh_time = 0;

				// Calculating the time till next refresh
				while (!f->is_refreshing_needed (f)) {
					g_get_current_time (&timeval);
					g_time_val_add (&timeval, 250000);
					g_cond_timed_wait (f->cond, f->mutex, &timeval);
				}
				
				g_timer_stop (elapsed);
				f->avg_refresh_time += g_timer_elapsed (elapsed, NULL);
				/*g_print ("%f secs added, so now time is %f\n",
						g_timer_elapsed (elapsed, NULL),
						f->avg_refresh_time);*/
				g_timer_destroy (elapsed);
				do_refresh = TRUE;

				// Now set timeout and go back into normal mode, if the number of
				// passes has been reached (remember we start and 0 and decrease
				// by one in each pass, so there have been X passes if we have
				// timeout at - (X-1))
				if (f->avg_refresh_timeout == -(AVG_REFRESH_TIME_NB_PASS - 1)) {
					f->avg_refresh_timeout = AUTO_REFRESH_FREQ_TIMEOUT_LEN;
					f->avg_refresh_time /= AVG_REFRESH_TIME_NB_PASS;
					/*g_print ("Timeout now set to %f, time = %f\n", 
								f->avg_refresh_timeout,
								f->avg_refresh_time);*/
				} else {
					f->avg_refresh_timeout--;
					/*g_print ("One pass done, %f passes left, time amt is %f\n",
							AVG_REFRESH_TIME_NB_PASS + f->avg_refresh_timeout,
								f->avg_refresh_time);*/
				}
			}
			//Timeout > 0, remaining time before next recalculation of average refresh freq
			else {
				f->avg_refresh_timeout -= f->avg_refresh_time;
				if (f->avg_refresh_timeout < 0)
					f->avg_refresh_timeout = 0; //Dont go in negatives to not screw the recalculation algorithm
				g_time_val_add (&timeval, f->avg_refresh_time * G_USEC_PER_SEC);
				g_cond_timed_wait (f->cond, f->mutex, &timeval);
				do_refresh = f->is_refreshing_needed (f);
				/*g_print ("Timeout = %f, time = %f\n",
								f->avg_refresh_timeout,
								f->avg_refresh_time);*/
			}
		}
			// We use a fixed refresh frequency here
		else {
			g_time_val_add (&timeval, inst->settings->refresh_freq * 1000); //Turn into usec
			g_cond_timed_wait (f->cond, f->mutex, &timeval);
			do_refresh = f->is_refreshing_needed (f);
		}

//		g_print ("%i:now calling refresh_content\n", do_refresh);
		if ((f->compute) && (do_refresh)) {
			f->show_spinner = TRUE;
			f->spinner_start_src = gdk_threads_add_timeout (500, frame_spinner_start, data);

			f->refresh_content (f);

			f->show_spinner = FALSE;
			f->spinner_stop_src = gdk_threads_add_idle (frame_spinner_stop, data);
		}/* else {
			g_print ("The ninja didn't want to refresh his mind about kittens.\n");
		}*/

		g_mutex_unlock (f->mutex);
	}

	return NULL;
}
